package com.threegold.listview;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.BaseItemProvider;
import ohos.agp.components.Component;
import ohos.agp.components.ListContainer;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ListView extends ListContainer implements Component.LayoutRefreshedListener,
        Component.ScrolledListener, Component.CanAcceptScrollListener, Component.TouchEventListener {

    /**
     * 打印日志的业务领域：ListView对应的类都使用业务领域值
     */
    public static final int LOG_DOMAIN_LIST = 111;
    /**
     * 打印log的标签
     */
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, LOG_DOMAIN_LIST, ListView.class.getSimpleName());

    /**
     * 每行默认展示的列数
     */
    public static final int DEFAULT_COLUMN = 1;

    /**
     * headerView集合
     */
    private final List<Component> headerViews = new ArrayList<>();
    /**
     * footerView集合
     */
    private final List<Component> footerViews = new ArrayList<>();
    /**
     * 是否开启加载更多
     */
    private boolean openLoadMore;
    /**
     * 加载更多的组件
     */
    private LoadMoreView loadMoreView;
    /**
     * 是否还有更多数据，默认是有的
     */
    private boolean hasMore = true;
    /**
     * 加载更多监听器
     */
    private OnLoadMoreListener onLoadMoreListener;
    /**
     * 当前加载更多的状态
     */
    private LoadState loadState = LoadState.NONE;
    /**
     * 执行滚动到底部的属性动画
     */
    private AnimatorValue scrollToBottomAnimator;
    /**
     * 是否锁定不让滚动
     */
    private boolean isLockScroll;
    /**
     * down事件触碰点y值
     */
    private float lastY;
    /**
     * 当前滑动是否上滑
     */
    private boolean isScrollUp;
    /**
     * 一行展示几列，默认值1，设置大于1的多列可以展示grid效果
     */
    private int columnNo = DEFAULT_COLUMN;

    /**
     * item点击事件监听器
     */
    private OnItemClickListener<?> onItemClickListener;
    /**
     * 长按事件
     */
    private OnItemLongClickListener<?> onItemLongClickListener;
    /**
     * 列（即垂直）分割线的颜色：默认透明色
     */
    private int columnDividerColor = Color.TRANSPARENT.getValue();
    /**
     * 列（即垂直）分割线的尺寸
     */
    private int columnDividerSize;
    /**
     * 多列的时候是否显示行左右的分割线
     */
    private boolean showColumnOuterDivider;
    /**
     * 行（即水平）分割线的颜色：默认透明色
     */
    private int rowDividerColor = Color.TRANSPARENT.getValue();
    /**
     * 行（即水平）分割线的尺寸
     */
    private int rowDividerSize;
    /**
     * 是否显示顶部的行分割线：也就是除开HeaderView和LoadMoreView的第一个item顶部的分割线
     */
    private boolean showTopRowDivider = true;
    /**
     * 是否显示底部的分割线：也就是除开LoadMoreView和HeaderView的最后一个item的底部分割线
     */
    private boolean showBottomRowDivider = true;
    /**
     * 绘制行分割线是否忽略当前行实际展示的列数：也就是每一行是否完整绘制行分割线。
     * 例如：
     * 一行总共展示3列，但最后一行实际只有一列数据，剩余2列没有，
     * 为fasle表示不会绘制没有实际列下面对应的分割线，为true代表会绘制。
     */
    private boolean rowDividerIgnoreDisplayColumn;
    /**
     * 没有数据时候展示的空组件
     */
    private Component emptyView;
    /**
     * 控制是否展示emptyView的标识
     */
    private boolean displayEmpty;

    public ListView(Context context) {
        this(context, null);
    }

    public ListView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public ListView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        // 目前做的功能只支持垂直方向，直接强制设置
        setOrientation(VERTICAL);
        // 关闭长按事件：默认是开启的，会触发item的拖拽
        setLongClickable(false);
        // 解决组件的默认bug
        setLayoutRefreshedListener(this);
    }

    /**
     * 设置emptyView
     *
     * 使用注意点：
     * 当ListView嵌套在ListRefreshLayout中使用的时候，emptyView中不推荐子组件使用相对于父组件的属性进行位置固定，会出现UI闪烁效果
     * 不推荐使用这类属性，例如：TextAlignment.HORIZONTAL_CENTER,LayoutAlignment.BOTTOM,LayoutAlignment.CENTER，也不推荐使用明显的背景色。
     * 推荐使用固定值定位，例如：marginTop=200
     *
     * 闪烁原因：
     * ListRefreshLayout继承自线性布局，当下拉刷新时总高度不变，而刷新的Header会逐渐展示，
     * 导致下方的ListView高度动态发生变化，从而导致内部的emptyView高度动态变化，使之内部的子组件位置随之变化
     */
    public ListView setEmptyView(Component emptyView) {
        this.emptyView = emptyView;
        if (emptyView != null) {
            // emptyView设置为填充满整个ListView
            LayoutConfig layoutConfig = emptyView.getLayoutConfig();
            if (layoutConfig == null) {
                layoutConfig = new LayoutConfig();
            }
            layoutConfig.width = LayoutConfig.MATCH_PARENT;
            layoutConfig.height = LayoutConfig.MATCH_PARENT;
            emptyView.setLayoutConfig(layoutConfig);
        }
        return this;
    }

    public Component getEmptyView() {
        return emptyView;
    }

    /**
     * 控制ListView展示emptyView
     * @param display 是否展示
     */
    public ListView displayEmptyView(boolean display) {
        if (this.displayEmpty != display) {
            this.displayEmpty = display;
        }
        return this;
    }

    public boolean isDisplayEmpty() {
        return displayEmpty;
    }

    public ListView setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
        return this;
    }

    OnItemClickListener getOnItemClickListener() {
        return onItemClickListener;
    }

    public ListView setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
        this.onItemLongClickListener = onItemLongClickListener;
        return this;
    }

    OnItemLongClickListener getOnItemLongClickListener() {
        return onItemLongClickListener;
    }

    public ListView setRowDividerIgnoreDisplayColumn(boolean ignore) {
        if (this.rowDividerIgnoreDisplayColumn != ignore) {
            this.rowDividerIgnoreDisplayColumn = ignore;
            notifyItemProvider();
        }
        return this;
    }

    boolean isRowDividerIgnoreDisplayColumn() {
        return rowDividerIgnoreDisplayColumn;
    }

    boolean isShowTopRowDivider() {
        return showTopRowDivider;
    }

    public ListView setShowTopRowDivider(boolean show) {
        if (this.showTopRowDivider != show) {
            this.showTopRowDivider = show;
            notifyItemProvider();
        }
        return this;
    }

    boolean isShowBottomRowDivider() {
        return showBottomRowDivider;
    }

    public ListView setShowBottomRowDivider(boolean show) {
        if (this.showBottomRowDivider != show) {
            this.showBottomRowDivider = show;
            notifyItemProvider();
        }
        return this;
    }

    public int getRowDividerSize() {
        return rowDividerSize;
    }

    public ListView setRowDividerSize(int size) {
        if (this.rowDividerSize != size) {
            this.rowDividerSize = size;
            notifyItemProvider();
        }
        return this;
    }

    public int getRowDividerColor() {
        return rowDividerColor;
    }

    public ListView setRowDividerColor(int color) {
        if (this.rowDividerColor != color) {
            this.rowDividerColor = color;
            notifyItemProvider();
        }
        return this;
    }

    public ListView setShowColumnOuterDivider(boolean show) {
        if (this.showColumnOuterDivider != show) {
            this.showColumnOuterDivider = show;
            notifyItemProvider();
        }
        return this;
    }

    boolean isShowColumnOuterDivider() {
        return showColumnOuterDivider;
    }

    public int getColumnDividerSize() {
        return columnDividerSize;
    }

    public ListView setColumnDividerSize(int size) {
        if (this.columnDividerSize != size) {
            this.columnDividerSize = size;
            notifyItemProvider();
        }
        return this;
    }

    public int getColumnDividerColor() {
        return columnDividerColor;
    }

    public ListView setColumnDividerColor(int color) {
        if (this.columnDividerColor != color) {
            this.columnDividerColor = color;
            notifyItemProvider();
        }
        return this;
    }

    /**
     * 设置一行展示的item个数
     */
    public ListView setColumnNo(int columnNo) {
        // 设置的列数必须大于0，并且与当前不同才生效
        if (this.columnNo != columnNo && columnNo > 0) {
            this.columnNo = columnNo;
            // 修改了column值，立即更新布局
            notifyItemProvider();
        }
        return this;
    }

    private void notifyItemProvider() {
        BaseItemProvider itemProvider = getItemProvider();
        if (itemProvider != null) {
            itemProvider.notifyDataChanged();
        }
    }

    public int getColumnNo() {
        return columnNo;
    }

    /**
     * 设置加载更多监听器
     */
    public ListView setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
        return this;
    }

    /**
     * 设置是否开启加载更多
     */
    public ListView setOpenLoadMore(boolean openLoadMore) {
        if (this.openLoadMore != openLoadMore) {
            this.openLoadMore = openLoadMore;
            notifyItemProvider();
        }
        return this;
    }

    /**
     * 获取是否开启加载更多
     */
    public boolean isOpenLoadMore() {
        return openLoadMore;
    }

    /**
     * 是否还有更多数据
     */
    public void setHasMore(boolean hasMore) {
        if (this.hasMore != hasMore) {
            this.hasMore = hasMore;
            if (loadMoreView != null) {
                loadMoreView.onCanLoadMoreChanged(hasMore);
            }
        }
    }

    /**
     * 是否正在加载更多
     */
    public boolean isLoadingMore() {
        return loadState == LoadState.LOADING;
    }

    public void loadComplete() {
        loadState = LoadState.NONE;
    }

    public int getFooterViewCount() {
        return footerViews.size();
    }

    public Component getFooterView(int position) {
        int footerCount = getFooterViewCount();
        if (position < footerCount) {
            return footerViews.get(position);
        }
        return null;
    }

    public ListView addFooterView(Component component) {
        if (component == null) {
            return this;
        }
        if (!footerViews.contains(component)) {
            resetItemLayoutConfig(component);
            footerViews.add(component);
        }
        return this;
    }

    public ListView removeFooterView(Component component) {
        if (component == null) {
            return this;
        }
        footerViews.remove(component);
        return this;
    }

    public int getHeaderViewCount() {
        return headerViews.size();
    }

    public Component getHeaderView(int position) {
        int headerCount = getHeaderViewCount();
        if (position < headerCount) {
            return headerViews.get(position);
        }
        return null;
    }

    /**
     * 添加HeaderView
     */
    public ListView addHeaderView(Component component) {
        if (component == null) {
            return this;
        }
        if (!headerViews.contains(component)) {
            resetItemLayoutConfig(component);
            headerViews.add(component);
        }
        return this;
    }

    /**
     * 移除headerView
     */
    public ListView removeHeaderView(Component component) {
        if (component == null) {
            return this;
        }
        headerViews.remove(component);
        return this;
    }

    /**
     * 设置加载更多组件
     */
    public ListView setLoadMoreView(LoadMoreView loadMoreView) {
        this.loadMoreView = loadMoreView;
        if (loadMoreView != null) {
            resetItemLayoutConfig(loadMoreView.getLayoutView());
        }
        return this;
    }

    /**
     * 重新设置组件的布局配置，让宽填充满父组件
     */
    private void resetItemLayoutConfig(Component component) {
        if (component == null) {
            return;
        }
        LayoutConfig layoutConfig = component.getLayoutConfig();
        if (layoutConfig == null) {
            layoutConfig = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT);
        } else {
            layoutConfig.width = LayoutConfig.MATCH_PARENT;
        }
        component.setLayoutConfig(layoutConfig);
    }

    /**
     * 获取加载更多组件
     */
    public Component getLoadMoreLayoutView() {
        return loadMoreView == null ? null : loadMoreView.getLayoutView();
    }

    /**
     * 这里只允许设置ListItemProvider的子类，否则报错
     */
    @Override
    public void setItemProvider(BaseItemProvider itemProvider) {
        // 这里使用ListView必须使用ListItemProvider，否则抛出异常
        if (!(itemProvider instanceof ListItemProvider)) {
            throw new RuntimeException("itemProvider must instance of ListItemProvider");
        }
        // 将ListView绑定到ListItemProvider
        ((ListItemProvider) itemProvider).bindListView(this);
        // 添加滚动监听
        addScrolledListener(this);
        setCanAcceptScrollListener(this);
        setTouchEventListener(this);
        super.setItemProvider(itemProvider);
    }

    @Override
    public void onRefreshed(Component component) {
        // 由于刷新数据后，如果item有多种，并且高度不一样，
        // 那么底部就会出现刷新数据之后出现一条空白没有item，这是组件的bug，
        // 要想解决该问题，就需要刷新数据后，让组件滚动达到加载效果
        scrollBy(0, -1);
    }

    private boolean canHandleScroll() {
        // 没有数据可加载，正在加载流程状态，没有设置监听，没有开启加载更多，都不能处理滚动
        return hasMore && loadState == LoadState.NONE && onLoadMoreListener != null && openLoadMore;
    }

    @Override
    public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        if (!canHandleScroll()) {
            return;
        }
        if (lastVisibleChildIsLoadMore()) {
            // 最后一个item，也就是loadMoreView刚展示，更改状态为进入加载流程
            loadState = LoadState.TO_LOAD;
        }
    }

    /**
     * 判断最后一个可见的item是否是loadMoreView
     * 类中掉用该方法前已经判断过是否开启了加载更多，这里就不多判断
     */
    private boolean lastVisibleChildIsLoadMore() {
        // 获取第一个可见的item的索引
        int itemPosByVisibleIndex = getItemPosByVisibleIndex(0);
        // 获取屏幕可见的item数量
        int visibleIndexCount = getVisibleIndexCount();
        // 获取ItemProvider总共设置的item数量
        int childCount = getItemProvider().getCount();
        // 当前屏幕最后展示的item = 总共item的数量，即最后一条item可见
        return itemPosByVisibleIndex + visibleIndexCount == childCount;
    }

    /**
     * 该方法主要用来重写，根据外部条件处理当前是否可以加载更多。
     * 例如：嵌套下来刷新时候，正在下拉刷新不能执行上拉加载
     */
    public boolean canExecuteLoadMore() {
        return true;
    }

    @Override
    public void scrolledStageUpdate(Component component, int newStage) {
        // 如果不能
        if (!canExecuteLoadMore()) {
            return;
        }
        // 滑动停止时，判断当前是否滑动到了loadMoreView的底部，如果不是，则需要执行一个动画，让整个ListView拉倒底部
        if (newStage == Component.SCROLL_IDLE_STAGE && loadState == LoadState.TO_LOAD) {
            boolean lastVisibleChildIsLoadMore = lastVisibleChildIsLoadMore();
            // 停止时候还需要判断最后一个可见的是不是loadMoreView
            if (!lastVisibleChildIsLoadMore) {
                // 此时loadMoreView不可见，代表并不是加载更多，直接重置状态
                loadState = LoadState.NONE;
                return;
            }
            // 由于item是ListView容器内部的结构，直接获取到的bottom的Y值是从ListView的第一个item顶点开始计算的，
            // 因此没有包含ListView的topPadding值，LoadMoreView实际拉倒底的y值 = LoadMoreView的bottom值 + ListView的topPadding值
            int bottomY = loadMoreView.getLayoutView().getBottom() + getPaddingTop();
            // 实际LoadMoreView最多能够滚动到的底部y值 = ListView的高度 - ListView的bottomPadding - LoadMoreView的bottomMargin
            int canScrollMaxY = getEstimatedHeight() - getPaddingBottom() - loadMoreView.getLayoutView().getMarginBottom();
            // 计算两个的差值，差值就是需要滑动的距离：注意这里bottomY必定是大于等于canScrollMaxY
            int deltaY = bottomY - canScrollMaxY;
            if (deltaY == 0) {
                // 已经滑动到了底部，这种情况认为都是触发加载更多
                callbackLoading();
            } else {
                // 需要执行动画这种情况，只有从上往下滑动才是加载更多
                if (!isScrollUp) {
                    loadState = LoadState.NONE;
                    return;
                }
                // 执行动画滚动到底部
                executeToBottomAnimator(deltaY);
            }
        }
    }

    /**
     * 执行动画，让loadMoreView拉倒底部
     * @param deltaY 执行动画的偏移长度
     */
    private void executeToBottomAnimator(int deltaY) {
        HiLog.debug(LABEL, "ListView start animation to bottom to loadingMore");
        isLockScroll = true;
        // 获取当前垂直方向已经滚动过的偏移量y值，以此作为基础滚动数据
        int scrollOffsetValue = ListView.this.getScrollValue(VERTICAL);
        if (scrollToBottomAnimator == null) {
            scrollToBottomAnimator = new AnimatorValue();
            scrollToBottomAnimator.setDuration(100);
        }
        scrollToBottomAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float val) {
                // 计算出具需要滚动到的y值
                int scrollY = (int) (scrollOffsetValue + val * deltaY);
                // 这里使用scrollTo滚动到具体的位置
                ListView.this.scrollTo(0, scrollY);
                if (val == 1) {
                    // 动画执行完成
                    callbackLoading();
                }
            }
        });
        scrollToBottomAnimator.start();
    }

    /**
     * 回调进入加载更多状态
     */
    private void callbackLoading() {
        HiLog.debug(LABEL, "ListView is start loadingMore");
        isLockScroll = false;
        isScrollUp = false;
        loadState = LoadState.LOADING;
        if (onLoadMoreListener != null) {
            onLoadMoreListener.onLoadingMore();
        }
    }

    @Override
    public boolean canAcceptScroll(Component component, int i, boolean b) {
        // 在手动滑动的过程中，当滚动停止时，可能会执行一个滚动到底的动画，执行动画期间不响应滚动事件，防止效果冲突
        return !isLockScroll;
    }

    /**
     * 计算是否滚动到了顶端
     */
    public boolean calculateIsScrollToTheTop() {
        int itemPosByVisibleIndex = getItemPosByVisibleIndex(0);
        // 是否显示了第一个child
        boolean showFirstChild = itemPosByVisibleIndex == 0;
        if (showFirstChild) {
            // 滑到第一个item
            // 注意，目前发现问题：如果有HeaderView，这里会出现获取0位置的itemView，也就是headerView为null的情况，导致crash
            // 因此这里判断，有headerView，直接获取对应的HeaderView，没有再调用getComponentAt去获取
            Component firstChild = getHeaderViewCount() > 0 ? getHeaderView(0) : getComponentAt(0);
            int firstChildTop = firstChild.getTop();
            return firstChildTop - firstChild.getMarginTop() == 0;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        // 未开启上拉加载，就不响应触摸事件
        if (!hasMore || !openLoadMore || loadMoreView == null) {
            return false;
        }
        // 处理触摸事件，这里主要使用来判断用户滑动操作是上滑还是下滑，会用来判断是否是正常的上拉加载动作
        MmiPoint point = touchEvent.getPointerScreenPosition(0);
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                lastY = point.getY();
                // 当滑动到了顶点，可以响应外部嵌套的下拉刷新组件：拉倒顶点，也就意味着当前不需要执行上拉加载
                boolean isScrollToTheTop = calculateIsScrollToTheTop();
                HiLog.debug(LABEL, "ListView onTouchEvent down scrollToTheTop:%{public}b", isScrollToTheTop);
                return !isScrollToTheTop;
            case TouchEvent.PRIMARY_POINT_UP:
                float upY = point.getY();
                isScrollUp = lastY > upY;
                break;
        }
        return true;
    }

    /**
     * 加载更多监听接口
     */
    public interface OnLoadMoreListener {
        void onLoadingMore();
    }

    /**
     * 处理加载更多的状态枚举
     */
    private enum LoadState {
        NONE,
        TO_LOAD,
        LOADING
    }

    /**
     * ViewHolder基类
     */
    public static class ViewHolder {
        /**
         * 当前ViewHolder对应的itemView
         */
        private final Component itemView;
        /**
         * 当前itemView对应的itemType
         */
        private final int itemViewType;
        /**
         * 缓存通过当前itemView获取的子组件集合
         */
        private final Map<Integer, Component> views = new HashMap<>();

        public ViewHolder(Component itemView, int itemViewType) {
            if (itemView == null) {
                throw new RuntimeException("itemView can not be null");
            }
            this.itemView = itemView;
            this.itemViewType = itemViewType;
        }

        public Component getItemView() {
            return itemView;
        }

        public int getItemViewType() {
            return itemViewType;
        }

        public Component getViewById(int resId) {
            if (views.containsKey(resId)) {
                return views.get(resId);
            }
            Component component = itemView.findComponentById(resId);
            if (component != null) {
                views.put(resId, component);
            }
            return component;
        }
    }

    /**
     * item点击监听器接口
     */
    public interface OnItemClickListener<T> {
        void onItemClick(Component component, T data, int position);
    }

    /**
     * item长按监听器接口
     */
    public interface OnItemLongClickListener<T> {
        void onItemLongClick(Component component, T data, int position);
    }
}
